// -------------------------------------------------
// BlinKit - BlinKit Library
// -------------------------------------------------
//   File Name: FontFace.cpp
// Description: FontFace Class
//      Author: Ziming Li
//     Created: 2021-07-19
// -------------------------------------------------
// Copyright (C) 2021 MingYang Software Technology.
// -------------------------------------------------

/*
 * Copyright (C) 2013 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "./FontFace.h"

#include "blinkit/blink/renderer/bindings/core/exception_state.h"
#include "blinkit/blink/renderer/core/CSSValueKeywords.h"
#include "blinkit/blink/renderer/core/css/BinaryDataFontFaceSource.h"
#include "blinkit/blink/renderer/core/css/CSSCustomIdentValue.h"
#include "blinkit/blink/renderer/core/css/CSSFontFace.h"
#include "blinkit/blink/renderer/core/css/CSSFontFaceSrcValue.h"
#include "blinkit/blink/renderer/core/css/CSSFontSelector.h"
#include "blinkit/blink/renderer/core/css/CSSPrimitiveValue.h"
#include "blinkit/blink/renderer/core/css/CSSUnicodeRangeValue.h"
#include "blinkit/blink/renderer/core/css/CSSValueList.h"
#include "blinkit/blink/renderer/core/css/FontFaceDescriptors.h"
#include "blinkit/blink/renderer/core/css/LocalFontFaceSource.h"
#include "blinkit/blink/renderer/core/css/RemoteFontFaceSource.h"
#include "blinkit/blink/renderer/core/css/StylePropertySet.h"
#include "blinkit/blink/renderer/core/css/StyleRule.h"
#include "blinkit/blink/renderer/core/css/parser/css_parser.h"
#include "blinkit/blink/renderer/core/dom/DOMArrayBuffer.h"
#include "blinkit/blink/renderer/core/dom/DOMArrayBufferView.h"
#include "blinkit/blink/renderer/core/dom/DOMException.h"
#include "blinkit/blink/renderer/core/dom/document.h"
#include "blinkit/blink/renderer/core/dom/ExceptionCode.h"
#include "blinkit/blink/renderer/core/dom/style_engine.h"
#include "blinkit/blink/renderer/core/frame/local_frame.h"
#include "blinkit/blink/renderer/core/frame/Settings.h"
#include "blinkit/blink/renderer/core/frame/UseCounter.h"
#include "blinkit/blink/renderer/platform/FontFamilyNames.h"
#include "blinkit/blink/renderer/platform/shared_buffer.h"

namespace blink {

static GCRefPtr<CSSValue> parseCSSValue(const Document* document, const String& value, CSSPropertyID propertyID)
{
    CSSParserContext context(*document);
    return CSSParser::parseFontFaceDescriptor(propertyID, value, context);
}

#if 0 // BKTODO:
PassRefPtrWillBeRawPtr<FontFace> FontFace::create(ExecutionContext* context, const AtomicString& family, StringOrArrayBufferOrArrayBufferView& source, const FontFaceDescriptors& descriptors)
{
    if (source.isString())
        return create(context, family, source.getAsString(), descriptors);
    if (source.isArrayBuffer())
        return create(context, family, source.getAsArrayBuffer(), descriptors);
    if (source.isArrayBufferView())
        return create(context, family, source.getAsArrayBufferView(), descriptors);
    ASSERT_NOT_REACHED();
    return nullptr;
}

PassRefPtrWillBeRawPtr<FontFace> FontFace::create(ExecutionContext* context, const AtomicString& family, const String& source, const FontFaceDescriptors& descriptors)
{
    RefPtrWillBeRawPtr<FontFace> fontFace = adoptRefWillBeNoop(new FontFace(context, family, descriptors));

    RefPtrWillBeRawPtr<CSSValue> src = parseCSSValue(toDocument(context), source, CSSPropertySrc);
    if (!src || !src->isValueList())
        fontFace->setError(DOMException::create(SyntaxError, "The source provided ('" + source + "') could not be parsed as a value list."));

    fontFace->initCSSFontFace(toDocument(context), src);
    return fontFace.release();
}

PassRefPtrWillBeRawPtr<FontFace> FontFace::create(ExecutionContext* context, const AtomicString& family, PassRefPtr<DOMArrayBuffer> source, const FontFaceDescriptors& descriptors)
{
    RefPtrWillBeRawPtr<FontFace> fontFace = adoptRefWillBeNoop(new FontFace(context, family, descriptors));
    fontFace->initCSSFontFace(static_cast<const unsigned char*>(source->data()), source->byteLength());
    return fontFace.release();
}

PassRefPtrWillBeRawPtr<FontFace> FontFace::create(ExecutionContext* context, const AtomicString& family, PassRefPtr<DOMArrayBufferView> source, const FontFaceDescriptors& descriptors)
{
    RefPtrWillBeRawPtr<FontFace> fontFace = adoptRefWillBeNoop(new FontFace(context, family, descriptors));
    fontFace->initCSSFontFace(static_cast<const unsigned char*>(source->baseAddress()), source->byteLength());
    return fontFace.release();
}
#endif

GCRefPtr<FontFace> FontFace::create(Document *document, const GCRefPtr<StyleRuleFontFace> &fontFaceRule)
{
    const StylePropertySet& properties = fontFaceRule->properties();

    // Obtain the font-family property and the src property. Both must be defined.
    GCRefPtr<CSSValue> family = properties.getPropertyCSSValue(CSSPropertyFontFamily);
    if (!family || (!family->isCustomIdentValue() && !family->isPrimitiveValue()))
        return nullptr;
    GCRefPtr<CSSValue> src = properties.getPropertyCSSValue(CSSPropertySrc);
    if (!src || !src->isValueList())
        return nullptr;

    GCRefPtr<FontFace> fontFace(new FontFace(document));

    if (fontFace->setFamilyValue(*family)
        && fontFace->setPropertyFromStyle(properties, CSSPropertyFontStyle)
        && fontFace->setPropertyFromStyle(properties, CSSPropertyFontWeight)
        && fontFace->setPropertyFromStyle(properties, CSSPropertyFontStretch)
        && fontFace->setPropertyFromStyle(properties, CSSPropertyUnicodeRange)
        && fontFace->setPropertyFromStyle(properties, CSSPropertyFontVariant)
        && fontFace->setPropertyFromStyle(properties, CSSPropertyFontFeatureSettings)
        && fontFace->setPropertyFromStyle(properties, CSSPropertyFontDisplay)
        && !fontFace->family().isEmpty()
        && fontFace->traits().bitfield()) {
        fontFace->initCSSFontFace(document, src);
        return fontFace;
    }
    return nullptr;
}

FontFace::FontFace(ExecutionContext* context)
    : ActiveDOMObject(context)
    , m_status(Unloaded)
{
    suspendIfNeeded();
}

FontFace::FontFace(ExecutionContext* context, const AtomicString& family, const FontFaceDescriptors& descriptors)
    : ActiveDOMObject(context)
    , m_family(family)
    , m_status(Unloaded)
{
    Document* document = toDocument(context);
    setPropertyFromString(document, descriptors.style(), CSSPropertyFontStyle);
    setPropertyFromString(document, descriptors.weight(), CSSPropertyFontWeight);
    setPropertyFromString(document, descriptors.stretch(), CSSPropertyFontStretch);
    setPropertyFromString(document, descriptors.unicodeRange(), CSSPropertyUnicodeRange);
    setPropertyFromString(document, descriptors.variant(), CSSPropertyFontVariant);
    setPropertyFromString(document, descriptors.featureSettings(), CSSPropertyFontFeatureSettings);

    suspendIfNeeded();
}

FontFace::~FontFace()
{
}

String FontFace::style() const
{
    return m_style ? m_style->cssText() : "normal";
}

String FontFace::weight() const
{
    return m_weight ? m_weight->cssText() : "normal";
}

String FontFace::stretch() const
{
    return m_stretch ? m_stretch->cssText() : "normal";
}

String FontFace::unicodeRange() const
{
    return m_unicodeRange ? m_unicodeRange->cssText() : "U+0-10FFFF";
}

String FontFace::variant() const
{
    return m_variant ? m_variant->cssText() : "normal";
}

String FontFace::featureSettings() const
{
    return m_featureSettings ? m_featureSettings->cssText() : "normal";
}

void FontFace::setStyle(ExecutionContext* context, const String& s, ExceptionState& exceptionState)
{
    setPropertyFromString(toDocument(context), s, CSSPropertyFontStyle, &exceptionState);
}

void FontFace::setWeight(ExecutionContext* context, const String& s, ExceptionState& exceptionState)
{
    setPropertyFromString(toDocument(context), s, CSSPropertyFontWeight, &exceptionState);
}

void FontFace::setStretch(ExecutionContext* context, const String& s, ExceptionState& exceptionState)
{
    setPropertyFromString(toDocument(context), s, CSSPropertyFontStretch, &exceptionState);
}

void FontFace::setUnicodeRange(ExecutionContext* context, const String& s, ExceptionState& exceptionState)
{
    setPropertyFromString(toDocument(context), s, CSSPropertyUnicodeRange, &exceptionState);
}

void FontFace::setVariant(ExecutionContext* context, const String& s, ExceptionState& exceptionState)
{
    setPropertyFromString(toDocument(context), s, CSSPropertyFontVariant, &exceptionState);
}

void FontFace::setFeatureSettings(ExecutionContext* context, const String& s, ExceptionState& exceptionState)
{
    setPropertyFromString(toDocument(context), s, CSSPropertyFontFeatureSettings, &exceptionState);
}

void FontFace::setPropertyFromString(const Document* document, const String& s, CSSPropertyID propertyID, ExceptionState* exceptionState)
{
    GCRefPtr<CSSValue> value = parseCSSValue(document, s, propertyID);
    if (value && setPropertyValue(value, propertyID))
        return;

    String message = "Failed to set '" + s + "' as a property value.";
    if (exceptionState)
        exceptionState->throwDOMException(SyntaxError, message);
    else
        setError(DOMException::create(SyntaxError, message));
}

bool FontFace::setPropertyFromStyle(const StylePropertySet& properties, CSSPropertyID propertyID)
{
    return setPropertyValue(properties.getPropertyCSSValue(propertyID), propertyID);
}

bool FontFace::setPropertyValue(const GCRefPtr<CSSValue> &value, CSSPropertyID propertyID)
{
    switch (propertyID) {
    case CSSPropertyFontStyle:
        m_style = value;
        break;
    case CSSPropertyFontWeight:
        m_weight = value;
        break;
    case CSSPropertyFontStretch:
        m_stretch = value;
        break;
    case CSSPropertyUnicodeRange:
        if (value && !value->isValueList())
            return false;
        m_unicodeRange = value;
        break;
    case CSSPropertyFontVariant:
        m_variant = value;
        break;
    case CSSPropertyFontFeatureSettings:
        m_featureSettings = value;
        break;
    case CSSPropertyFontDisplay:
        m_display = value;
        break;
    default:
        ASSERT_NOT_REACHED();
        return false;
    }
    return true;
}

bool FontFace::setFamilyValue(const CSSValue& familyValue)
{
    AtomicString family;
    if (familyValue.isCustomIdentValue()) {
        family = AtomicString(toCSSCustomIdentValue(familyValue).value());
    } else if (toCSSPrimitiveValue(familyValue).isValueID()) {
        // We need to use the raw text for all the generic family types, since @font-face is a way of actually
        // defining what font to use for those types.
        switch (toCSSPrimitiveValue(familyValue).getValueID()) {
        case CSSValueSerif:
            family =  FontFamilyNames::webkit_serif;
            break;
        case CSSValueSansSerif:
            family =  FontFamilyNames::webkit_sans_serif;
            break;
        case CSSValueCursive:
            family =  FontFamilyNames::webkit_cursive;
            break;
        case CSSValueFantasy:
            family =  FontFamilyNames::webkit_fantasy;
            break;
        case CSSValueMonospace:
            family =  FontFamilyNames::webkit_monospace;
            break;
        case CSSValueWebkitPictograph:
            family =  FontFamilyNames::webkit_pictograph;
            break;
        default:
            return false;
        }
    }
    m_family = family;
    return true;
}

String FontFace::status() const
{
    switch (m_status) {
    case Unloaded:
        return "unloaded";
    case Loading:
        return "loading";
    case Loaded:
        return "loaded";
    case Error:
        return "error";
    default:
        ASSERT_NOT_REACHED();
    }
    return emptyString();
}

void FontFace::setLoadStatus(LoadStatus status)
{
    m_status = status;
    ASSERT(m_status != Error || m_error);

    if (m_status == Loaded || m_status == Error) {
#if 0 // BKTODO:
        if (m_loadedProperty) {
            if (m_status == Loaded)
                m_loadedProperty->resolve(this);
            else
                m_loadedProperty->reject(m_error.get());
        }
#endif

        ASSERT(m_callbacks.empty()); // BKTODO:
#if 0
        WillBeHeapVector<RefPtrWillBeMember<LoadFontCallback>> callbacks;
        m_callbacks.swap(callbacks);
        for (size_t i = 0; i < callbacks.size(); ++i) {
            if (m_status == Loaded)
                callbacks[i]->notifyLoaded(this);
            else
                callbacks[i]->notifyError(this);
        }
#endif
    }
}

void FontFace::setError(DOMException* error)
{
    if (!m_error)
        m_error = error ? error : DOMException::create(NetworkError);
    setLoadStatus(Error);
}

#if 0 // BKTODO:
ScriptPromise FontFace::fontStatusPromise(ScriptState* scriptState)
{
    if (!m_loadedProperty) {
        m_loadedProperty = new LoadedProperty(scriptState->executionContext(), this, LoadedProperty::Loaded);
        if (m_status == Loaded)
            m_loadedProperty->resolve(this);
        else if (m_status == Error)
            m_loadedProperty->reject(m_error.get());
    }
    return m_loadedProperty->promise(scriptState->world());
}

ScriptPromise FontFace::load(ScriptState* scriptState)
{
    loadInternal(scriptState->executionContext());
    return fontStatusPromise(scriptState);
}
#endif

void FontFace::loadWithCallback(PassRefPtrWillBeRawPtr<LoadFontCallback> callback, ExecutionContext* context)
{
    loadInternal(context);
    if (m_status == Loaded)
        callback->notifyLoaded(this);
    else if (m_status == Error)
        callback->notifyError(this);
    else
        m_callbacks.emplace_back(callback);
}

void FontFace::loadInternal(ExecutionContext* context)
{
    if (m_status != Unloaded)
        return;

    m_cssFontFace->load();
    toDocument(context)->styleEngine().fontSelector()->fontLoader()->loadPendingFonts();
}

FontTraits FontFace::traits() const
{
    FontStretch stretch = FontStretchNormal;
    if (m_stretch) {
        if (!m_stretch->isPrimitiveValue())
            return 0;

        switch (toCSSPrimitiveValue(m_stretch.get())->getValueID()) {
        case CSSValueUltraCondensed:
            stretch = FontStretchUltraCondensed;
            break;
        case CSSValueExtraCondensed:
            stretch = FontStretchExtraCondensed;
            break;
        case CSSValueCondensed:
            stretch = FontStretchCondensed;
            break;
        case CSSValueSemiCondensed:
            stretch = FontStretchSemiCondensed;
            break;
        case CSSValueSemiExpanded:
            stretch = FontStretchSemiExpanded;
            break;
        case CSSValueExpanded:
            stretch = FontStretchExpanded;
            break;
        case CSSValueExtraExpanded:
            stretch = FontStretchExtraExpanded;
            break;
        case CSSValueUltraExpanded:
            stretch = FontStretchUltraExpanded;
            break;
        default:
            break;
        }
    }

    FontStyle style = FontStyleNormal;
    if (m_style) {
        if (!m_style->isPrimitiveValue())
            return 0;

        switch (toCSSPrimitiveValue(m_style.get())->getValueID()) {
        case CSSValueNormal:
            style = FontStyleNormal;
            break;
        case CSSValueOblique:
            style = FontStyleOblique;
            break;
        case CSSValueItalic:
            style = FontStyleItalic;
            break;
        default:
            break;
        }
    }

    FontWeight weight = FontWeight400;
    if (m_weight) {
        if (!m_weight->isPrimitiveValue())
            return 0;

        switch (toCSSPrimitiveValue(m_weight.get())->getValueID()) {
        case CSSValueBold:
        case CSSValue700:
            weight = FontWeight700;
            break;
        case CSSValueNormal:
        case CSSValue400:
            weight = FontWeight400;
            break;
        case CSSValue900:
            weight = FontWeight900;
            break;
        case CSSValue800:
            weight = FontWeight800;
            break;
        case CSSValue600:
            weight = FontWeight600;
            break;
        case CSSValue500:
            weight = FontWeight500;
            break;
        case CSSValue300:
            weight = FontWeight300;
            break;
        case CSSValue200:
            weight = FontWeight200;
            break;
        case CSSValue100:
            weight = FontWeight100;
            break;
        // Although 'lighter' and 'bolder' are valid keywords for font-weights, they are invalid
        // inside font-face rules so they are ignored. Reference: http://www.w3.org/TR/css3-fonts/#descdef-font-weight.
        case CSSValueLighter:
        case CSSValueBolder:
            break;
        default:
            ASSERT_NOT_REACHED();
            break;
        }
    }

    FontVariant variant = FontVariantNormal;
    if (GCRefPtr<CSSValue> fontVariant = m_variant) {
        // font-variant descriptor can be a value list.
        if (fontVariant->isPrimitiveValue()) {
            GCRefPtr<CSSValueList> list = CSSValueList::createCommaSeparated();
            list->append(fontVariant);
            fontVariant = list;
        } else if (!fontVariant->isValueList()) {
            return 0;
        }

        CSSValueList* variantList = toCSSValueList(fontVariant.get());
        unsigned numVariants = variantList->length();
        if (!numVariants)
            return 0;

        for (unsigned i = 0; i < numVariants; ++i) {
            switch (toCSSPrimitiveValue(variantList->item(i))->getValueID()) {
            case CSSValueNormal:
                variant = FontVariantNormal;
                break;
            case CSSValueSmallCaps:
                variant = FontVariantSmallCaps;
                break;
            default:
                break;
            }
        }
    }

    return FontTraits(style, variant, weight, stretch);
}

static FontDisplay CSSValueToFontDisplay(CSSValue* value)
{
    if (value && value->isPrimitiveValue()) {
        switch (toCSSPrimitiveValue(value)->getValueID()) {
        case CSSValueAuto:
            return FontDisplayAuto;
        case CSSValueBlock:
            return FontDisplayBlock;
        case CSSValueSwap:
            return FontDisplaySwap;
        case CSSValueFallback:
            return FontDisplayFallback;
        case CSSValueOptional:
            return FontDisplayOptional;
        default:
            break;
        }
    }
    return FontDisplayAuto;
}

static std::unique_ptr<CSSFontFace> createCSSFontFace(FontFace* fontFace, CSSValue* unicodeRange)
{
    Vector<CSSFontFace::UnicodeRange> ranges;
    if (CSSValueList* rangeList = toCSSValueList(unicodeRange)) {
        unsigned numRanges = rangeList->length();
        for (unsigned i = 0; i < numRanges; i++) {
            CSSUnicodeRangeValue* range = toCSSUnicodeRangeValue(rangeList->item(i));
            ranges.append(CSSFontFace::UnicodeRange(range->from(), range->to()));
        }
    }

    return std::make_unique<CSSFontFace>(fontFace, ranges);
}

void FontFace::initCSSFontFace(Document* document, const GCRefPtr<CSSValue> &src)
{
    m_cssFontFace = createCSSFontFace(this, m_unicodeRange.get());
    if (m_error)
        return;

    // Each item in the src property's list is a single CSSFontFaceSource. Put them all into a CSSFontFace.
    ASSERT(src);
    ASSERT(src->isValueList());
    CSSValueList* srcList = toCSSValueList(src.get());
    int srcLength = srcList->length();

    for (int i = 0; i < srcLength; i++) {
        // An item in the list either specifies a string (local font name) or a URL (remote font to download).
        CSSFontFaceSrcValue* item = toCSSFontFaceSrcValue(srcList->item(i));
        std::unique_ptr<CSSFontFaceSource> source;

        if (!item->isLocal()) {
            bool allowDownloading = Settings::downloadableBinaryFontsEnabled();
            if (allowDownloading && item->isSupportedFormat() && document) {
                FontResource* fetched = item->fetch(document);
                if (fetched) {
                    FontLoader* fontLoader = document->styleEngine().fontSelector()->fontLoader();
                    source = std::make_unique<RemoteFontFaceSource>(fetched, fontLoader, CSSValueToFontDisplay(m_display.get()));
                }
            }
        } else {
            source = std::make_unique<LocalFontFaceSource>(item->resource());
        }

        if (source)
            m_cssFontFace->addSource(std::move(source));
    }
}

void FontFace::initCSSFontFace(const unsigned char* data, size_t size)
{
    m_cssFontFace = createCSSFontFace(this, m_unicodeRange.get());
    if (m_error)
        return;

    std::shared_ptr<SharedBuffer> buffer = SharedBuffer::create(data, size);
    std::unique_ptr<BinaryDataFontFaceSource> source = std::make_unique<BinaryDataFontFaceSource>(buffer.get(), m_otsParseMessage);
    if (source->isValid())
        setLoadStatus(Loaded);
    else
        setError(DOMException::create(SyntaxError, "Invalid font data in ArrayBuffer."));
    m_cssFontFace->addSource(std::move(source));
}

DEFINE_TRACE(FontFace)
{
    visitor->trace(m_style);
    visitor->trace(m_weight);
    visitor->trace(m_stretch);
    visitor->trace(m_unicodeRange);
    visitor->trace(m_variant);
    visitor->trace(m_featureSettings);
    visitor->trace(m_display);
    visitor->trace(m_error);
    // BKTODO: visitor->trace(m_loadedProperty);
    visitor->trace(m_callbacks);
    ActiveDOMObject::trace(visitor);
}

bool FontFace::hadBlankText() const
{
    return m_cssFontFace->hadBlankText();
}

bool FontFace::hasPendingActivity() const
{
    return m_status == Loading && executionContext() && !executionContext()->activeDOMObjectsAreStopped();
}

} // namespace blink
